/* 
 *  Copyright (C) 2009-2010 milkyjing <milkyjing@gmail.com>
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */

#ifndef MAP_H
#define MAP_H
#pragma once

#include <memory.h>
#include <stdlib.h>

typedef void (*CopyFunc)(void *, void *, const void *);    /* Map, dst, src */
typedef int (*CompareFunc)(const void *, const void *, const void *);    /* Map, dst, src */
typedef void (*CleanFunc)(void *);    /* Map */

/* Note that if element of map is a pointer, then the copy including map_assign(), map_retrieve() map_push_back() and map_copy() are all low copy. */
typedef struct _map {
    unsigned char *keyBuf;
    unsigned char *valueBuf;
    unsigned long keySize;
    unsigned long valueSize;
    unsigned long capacity;
    unsigned long count;
    CopyFunc copy_key;
    CopyFunc copy_value;
    CompareFunc compare_key;
    CompareFunc compare_value;
    CleanFunc clean_key_buf;
    CleanFunc clean_value_buf;
    int offset;    /* a key may be assigned with multiple values, so offset is used to specify which value to retrieve */
} Map;

#ifdef __cplusplus
extern "C" {
#endif    /* __cplusplus */

extern void map_default_copy_key(void *m, void *dst, const void *src) {
    memcpy(dst, src, ((Map *)m)->keySize);
}

extern void map_default_copy_value(void *m, void *dst, const void *src) {
    memcpy(dst, src, ((Map *)m)->valueSize);
}

extern int map_default_compare_key(const void *m, const void *key1, const void *key2) {
    if (memcmp(key1, key2, ((const Map *)m)->keySize) == 0) return 1;
    return 0;
}

extern int map_default_compare_value(const void *m, const void *value1, const void *value2) {
    if (memcmp(value1, value2, ((const Map *)m)->valueSize) == 0) return 1;
    return 0;
}

extern void map_default_clean_key_buf(void *m) {
    free(((Map *)m)->keyBuf);
}

extern void map_default_clean_value_buf(void *m) {
    free(((Map *)m)->valueBuf);
}

extern void map_init(Map *m, unsigned long keySize, unsigned long valueSize, unsigned long capacity, CopyFunc copy_key, CopyFunc copy_value, CompareFunc compare_key, CompareFunc compare_value, CleanFunc clean_key_buf, CleanFunc clean_value_buf) {
    Map map;
    map.keySize = keySize;
    map.valueSize = valueSize;
    map.capacity = capacity;
    map.count = 0;
    map.copy_key = copy_key;
    map.copy_value = copy_value;
    map.compare_key = compare_key;
    map.compare_value = compare_value;
    map.clean_key_buf = clean_key_buf;
    map.clean_value_buf = clean_value_buf;
    map.offset = 0;
    map.keyBuf = (unsigned char *)malloc(map.capacity * map.keySize);
    memset(map.keyBuf, 0, map.capacity * map.keySize);
    map.valueBuf = (unsigned char *)malloc(map.capacity * map.valueSize);
    memset(map.valueBuf, 0, map.capacity * map.valueSize);
    *m = map;
}

extern unsigned long map_get_size(const Map *m) {
    return m->count;
}

extern int map_get_offset(const Map *m) {
    return m->offset;
}

extern void map_set_offset(Map *m, int offset) {
    m->offset = offset;
}

extern void map_reset_offset(Map *m) {
    m->offset = 0;
}

extern void map_increase_offset(Map *m) {
    m->offset ++;
}

extern int map_assign(Map *m, const void *key, const void *value) {
    if (m->count >= m->capacity) return -1;
    m->copy_key(m, m->keyBuf + m->count * m->keySize, key);
    m->copy_value(m, m->valueBuf + m->count * m->valueSize, value);
    m->count ++;
    return 0;
}

extern int map_retrieve(Map *m, const void *key, void *value) {
    unsigned long i;
    int offCount;
    offCount = 0;
    for (i = 0; i < m->count; i ++) {
        if (m->compare_key(m, m->keyBuf + i * m->keySize, key)) {
            if (offCount == m->offset) break;
            else offCount ++;
        }
    }
    if (i == m->count) return -1;
    m->copy_value(m, value, m->valueBuf + i * m->valueSize);
    return 0;
}

extern void map_push_back(Map *m, const void *key, const void *value) {
    int prevCapacity;
    prevCapacity = m->capacity;
    if (m->count == m->capacity) {
        m->capacity += m->capacity / 2 + 1;
        m->keyBuf = (unsigned char *)realloc(m->keyBuf, m->capacity * m->keySize);
        memset(m->keyBuf + prevCapacity * m->keySize, 0, (m->capacity - prevCapacity) * m->keySize);
        m->valueBuf = (unsigned char *)realloc(m->valueBuf, m->capacity * m->valueSize);
        memset(m->valueBuf + prevCapacity * m->valueSize, 0, (m->capacity - prevCapacity) * m->valueSize);
    }
    m->copy_key(m, m->keyBuf + m->count * m->keySize, key);
    m->copy_value(m, m->valueBuf + m->count * m->valueSize, value);
    m->count ++;
}

extern int map_pop_back(Map *m) {
    if (0 == m->count) return -1;
    m->count --;
    return 0;
}

extern void map_clear(Map *m) {
    m->clean_key_buf(m);
    m->clean_value_buf(m);
    m->keyBuf = NULL;
    m->valueBuf = NULL;
    m->capacity = 0;
    m->count = 0;
    m->offset = 0;
    /* m->keySize and m->valueSize should stay unchanged */
}

extern void map_copy(Map *dst, const Map *src) {
    dst->keySize = src->keySize;
    dst->valueSize = src->valueSize;
    dst->capacity = src->capacity;
    dst->count = src->count;
    dst->copy_key = src->copy_key;
    dst->copy_value = src->copy_value;
    dst->compare_key = src->compare_key;
    dst->compare_value = src->compare_value;
    dst->clean_key_buf = src->clean_key_buf;
    dst->clean_value_buf = src->clean_value_buf;
    dst->offset = src->offset;
    dst->keyBuf = (unsigned char *)malloc(dst->capacity * dst->keySize);
    dst->valueBuf = (unsigned char *)malloc(dst->capacity * dst->valueSize);
    if (map_default_copy_key == src->copy_key)
        memcpy(dst->keyBuf, src->keyBuf, src->count * src->keySize);
    else {
        unsigned long i;
        for (i = 0; i < src->count; i ++)
            dst->copy_key(dst, dst->keyBuf + i * dst->keySize, src->keyBuf + i * src->keySize);
    }
    if (map_default_copy_value == src->copy_value)
        memcpy(dst->valueBuf, src->valueBuf, src->count * src->valueSize);
    else {
        unsigned long i;
        for (i = 0; i < src->count; i ++)
            dst->copy_value(dst, dst->valueBuf + i * dst->valueSize, src->valueBuf + i * src->valueSize);
    }
}

#ifdef __cplusplus
}
#endif    /* __cplusplus */

#endif    /* MAP_H */
